Imports System.Collections.Generic
Imports System.Diagnostics
Imports System.Threading
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Controls.Primitives
Imports System.Windows.Input
Imports System.Windows.Interop
Imports System.Windows.Media
Imports System.Windows.Shapes
Imports System.Windows.Threading
Imports Biller.provider
Imports Microsoft.Windows.Themes

Namespace core
	Public Class AutoCompleteManager
		Private Const WM_NCLBUTTONDOWN As Integer = &Ha1
		Private Const WM_NCRBUTTONDOWN As Integer = &Ha4

		Private Const POPUP_SHADOW_DEPTH As Integer = 5

		'+---------------------------------------------------------------------+
'          |                                                                     |
'          |                  Internal States                                    |
'          |                                                                     |
'          +---------------------------------------------------------------------


		Private _itemHeight As Double
		Private _downWidth As Double
		Private _downHeight As Double
		Private _downTop As Double
		Private _ptDown As Point

		Private _popupOnTop As Boolean = True
		Private _manualResized As Boolean
		Private _textBeforeChangedByCode As String
		Private _textChangedByCode As Boolean

		Private _textBox As TextBox
		Private _popup As Popup
        Private _chrome As SystemDropShadowChrome
		Private _listBox As ListBox
		Private _scrollBar As ScrollBar
		Private _resizeGrip As ResizeGrip
		Private _scrollViewer As ScrollViewer
		Private _asyncThread As Thread

		Private _dataProvider As IAutoCompleteDataProvider
		Private _disabled As Boolean
        Private _asynchronous As Boolean
        Private _popuponfocus As Boolean
		Private _autoAppend As Boolean
		Private _supressAutoAppend As Boolean

		'+---------------------------------------------------------------------+
'          |                                                                     |
'          |                     Public interface                                |
'          |                                                                     |
'          +---------------------------------------------------------------------


		Public Property DataProvider() As IAutoCompleteDataProvider
			Get
				Return _dataProvider
			End Get
			Set
				_dataProvider = value
			End Set
		End Property

		Public Property Disabled() As Boolean
			Get
				Return _disabled
			End Get
			Set
				_disabled = value
				If _disabled AndAlso _popup IsNot Nothing Then
					_popup.IsOpen = False
				End If
			End Set
		End Property

		Public ReadOnly Property AutoCompleting() As Boolean
			Get
				Return _popup.IsOpen
			End Get
		End Property

		Public Property Asynchronous() As Boolean
			Get
				Return _asynchronous
			End Get
			Set
				_asynchronous = value
			End Set
        End Property

        Public Property ShowPopupOnFocus() As Boolean
            Get
                Return _popuponfocus
            End Get
            Set(ByVal value As Boolean)
                _popuponfocus = value
            End Set
        End Property

		Public Property AutoAppend() As Boolean
			Get
				Return _autoAppend
			End Get
			Set
				_autoAppend = value
			End Set
		End Property

		'+---------------------------------------------------------------------+
'          |                                                                     |
'          |                       Initialier                                    |
'          |                                                                     |
'          +---------------------------------------------------------------------


				' default constructor
		Public Sub New()
		End Sub

		Public Sub New(textBox As TextBox)
			AttachTextBox(textBox)
		End Sub

		Public Sub AttachTextBox(textBox As TextBox)
			Debug.Assert(_textBox Is Nothing)
			If Application.Current.Resources.FindName("AcTb_ListBoxStyle") Is Nothing Then
				Dim myResourceDictionary = New ResourceDictionary()
                Dim uri = New Uri("Controls/AutoCompleteTextBox/resource/Resources.xaml", UriKind.Relative)
				myResourceDictionary.Source = uri
				Application.Current.Resources.MergedDictionaries.Add(myResourceDictionary)
			End If

			'
			_textBox = textBox
			Dim ownerWindow = Window.GetWindow(_textBox)
			If ownerWindow.IsLoaded Then
				Initialize()
			Else
				AddHandler ownerWindow.Loaded, AddressOf OwnerWindow_Loaded
			End If
			AddHandler ownerWindow.LocationChanged, AddressOf OwnerWindow_LocationChanged

			'
			'_dataProvider = new FileSysDataProvider();
		End Sub

        Private Sub OwnerWindow_LocationChanged(sender As Object, e As EventArgs)
            If _popup IsNot Nothing Then
                _popup.IsOpen = False
            End If
        End Sub

		Private Sub OwnerWindow_Loaded(sender As Object, e As RoutedEventArgs)
			Initialize()
		End Sub

		Private Sub Initialize()
			_listBox = New ListBox()
            Dim tempItem = New ListBoxItem() With { _
             .Content = "TEMP_ITEM_FOR_MEASUREMENT" _
            }
			_listBox.Items.Add(tempItem)
			_listBox.Focusable = False
			_listBox.Style = DirectCast(Application.Current.Resources("AcTb_ListBoxStyle"), Style)

			_chrome = New SystemDropShadowChrome()
			_chrome.Margin = New Thickness(0, 0, POPUP_SHADOW_DEPTH, POPUP_SHADOW_DEPTH)
			_chrome.Child = _listBox

			_popup = New Popup()
			_popup.SnapsToDevicePixels = True
			_popup.AllowsTransparency = True
			_popup.MinHeight = SystemParameters.HorizontalScrollBarHeight + POPUP_SHADOW_DEPTH
			_popup.MinWidth = SystemParameters.VerticalScrollBarWidth + POPUP_SHADOW_DEPTH
			_popup.VerticalOffset = SystemParameters.PrimaryScreenHeight + 100
			_popup.Child = _chrome
            _popup.IsOpen = True

			_itemHeight = tempItem.ActualHeight
			_listBox.Items.Clear()

			'
			GetInnerElementReferences()
			UpdateGripVisual()
            SetupEventHandlers()
            _popup.IsOpen = False
		End Sub

		Private Sub GetInnerElementReferences()
			_scrollViewer = TryCast(TryCast(_listBox.Template.FindName("Border", _listBox), Border).Child, ScrollViewer)
			_resizeGrip = TryCast(_scrollViewer.Template.FindName("ResizeGrip", _scrollViewer), ResizeGrip)
			_scrollBar = TryCast(_scrollViewer.Template.FindName("PART_VerticalScrollBar", _scrollViewer), ScrollBar)
		End Sub

		Private Sub UpdateGripVisual()
			Dim rectSize = SystemParameters.VerticalScrollBarWidth
			Dim triangle = TryCast(_resizeGrip.Template.FindName("RG_TRIANGLE", _resizeGrip), Path)
			Dim pg = TryCast(triangle.Data, PathGeometry)
			pg = pg.CloneCurrentValue()
			Dim figure = pg.Figures(0)
			Dim p = figure.StartPoint
			p.X = rectSize
			figure.StartPoint = p
			Dim line = TryCast(figure.Segments(0), PolyLineSegment)
			p = line.Points(0)
			p.Y = rectSize
			line.Points(0) = p
			p = line.Points(1)
			p.X = InlineAssignHelper(p.Y, rectSize)
			line.Points(1) = p
			triangle.Data = pg
		End Sub

		Private Sub SetupEventHandlers()
			Dim ownerWindow = Window.GetWindow(_textBox)
			AddHandler ownerWindow.PreviewMouseDown, AddressOf OwnerWindow_PreviewMouseDown
			AddHandler ownerWindow.Deactivated, AddressOf OwnerWindow_Deactivated

			Dim wih = New WindowInteropHelper(ownerWindow)
			Dim hwndSource__1 = HwndSource.FromHwnd(wih.Handle)
			Dim hwndSourceHook = New HwndSourceHook(AddressOf HookHandler)
			hwndSource__1.AddHook(hwndSourceHook)
			'hwndSource.RemoveHook();?

			AddHandler _textBox.TextChanged, AddressOf TextBox_TextChanged
            AddHandler _textBox.PreviewKeyDown, AddressOf TextBox_PreviewKeyDown
            AddHandler _textBox.GotFocus, AddressOf TextBox_GotFocus

			AddHandler _listBox.PreviewMouseLeftButtonDown, AddressOf ListBox_PreviewMouseLeftButtonDown
			AddHandler _listBox.MouseLeftButtonUp, AddressOf ListBox_MouseLeftButtonUp
			AddHandler _listBox.PreviewMouseMove, AddressOf ListBox_PreviewMouseMove

			AddHandler _resizeGrip.PreviewMouseLeftButtonDown, AddressOf ResizeGrip_PreviewMouseLeftButtonDown
			AddHandler _resizeGrip.PreviewMouseMove, AddressOf ResizeGrip_PreviewMouseMove
			AddHandler _resizeGrip.PreviewMouseUp, AddressOf ResizeGrip_PreviewMouseUp
		End Sub

		'+---------------------------------------------------------------------+
'          |                                                                     |
'          |                   TextBox Event Handling                            |
'          |                                                                     |
'          +---------------------------------------------------------------------

		Private Sub TextBox_TextChanged(sender As Object, e As TextChangedEventArgs)
            If _textChangedByCode OrElse Disabled OrElse _dataProvider Is Nothing Then
                Return
            End If

            Dim text = _textBox.Text
            usedtext = text
            If String.IsNullOrEmpty(text) Then
                If _popuponfocus Then
                Else
                    _popup.IsOpen = False
                    Return
                End If
            End If
			If _asynchronous Then
                If _asyncThread IsNot Nothing AndAlso _asyncThread.IsAlive Then
                    _asyncThread.Abort()
                End If
                _asyncThread = New Thread(AddressOf something)
                _asyncThread.Start()
			Else
				Dim items = _dataProvider.GetItems(text)
				PopulatePopupList(items)
			End If
        End Sub

        Private Sub TextBox_GotFocus(ByVal sender As Object, ByVal e As RoutedEventArgs)
            If _popuponfocus Then
                If _asynchronous Then

                    If _asyncThread IsNot Nothing AndAlso _asyncThread.IsAlive Then
                        _asyncThread.Abort()
                    End If
                    _asyncThread = New Thread(AddressOf something)
                    _asyncThread.Start()
                Else
                    Dim items = _dataProvider.GetItems(usedtext)
                    PopulatePopupList(items)
                End If
            End If
        End Sub

        Private usedtext As String
        Private Function something()
            Try
                Dim items = _dataProvider.GetItems(usedtext)
                Dim dispatcher = Application.Current.Dispatcher
                Dim currentText = dispatcher.Invoke(New Func(Of String)(Function() _textBox.Text)).ToString()
                If usedtext <> currentText Then
                    Return Nothing
                End If
                dispatcher.Invoke(New Action(Function() PopulatePopupList(items)))
            Catch ex As Exception

            End Try
        End Function

        Private Sub TextBox_PreviewKeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
            _supressAutoAppend = e.Key = Key.Delete OrElse e.Key = Key.Back
            If Not _popup.IsOpen Then
                Return
            End If
            If e.Key = Key.Enter Then
                _popup.IsOpen = False
                _textBox.SelectAll()
            ElseIf e.Key = Key.Tab Then
                _popup.IsOpen = False
                _textBox.SelectAll()
            ElseIf e.Key = Key.Escape Then
                _popup.IsOpen = False
                e.Handled = True
            End If
            If Not _popup.IsOpen Then
                Return
            End If
            Dim index = _listBox.SelectedIndex
            If e.Key = Key.PageUp Then
                If index = -1 Then
                    index = _listBox.Items.Count - 1
                ElseIf index = 0 Then
                    index = -1
                ElseIf index = _scrollBar.Value Then
                    index -= CInt(Math.Truncate(_scrollBar.ViewportSize))
                    If index < 0 Then
                        index = 0
                    End If
                Else
                    index = CInt(Math.Truncate(_scrollBar.Value))
                End If
            ElseIf e.Key = Key.PageDown Then
                If index = -1 Then
                    index = 0
                ElseIf index = _listBox.Items.Count - 1 Then
                    index = -1
                ElseIf index = _scrollBar.Value + _scrollBar.ViewportSize - 1 Then
                    index += CInt(Math.Truncate(_scrollBar.ViewportSize)) - 1
                    If index > _listBox.Items.Count - 1 Then
                        index = _listBox.Items.Count - 1
                    End If
                Else
                    index = CInt(Math.Truncate(_scrollBar.Value + _scrollBar.ViewportSize - 1))
                End If
            ElseIf e.Key = Key.Up Then
                If index = -1 Then
                    index = _listBox.Items.Count - 1
                Else
                    index -= 1
                End If
            ElseIf e.Key = Key.Down Then
                index += 1
            End If

            If index <> _listBox.SelectedIndex Then
                Dim text As String
                If index < 0 OrElse index > _listBox.Items.Count - 1 Then
                    text = _textBeforeChangedByCode
                    _listBox.SelectedIndex = -1
                Else
                    _listBox.SelectedIndex = index
                    _listBox.ScrollIntoView(_listBox.SelectedItem)
                    text = TryCast(_listBox.SelectedItem, String)
                End If
                UpdateText(text, False)
                e.Handled = True
            End If
        End Sub

        '+---------------------------------------------------------------------+
        '          |                                                                     |
        '          |                     ListBox Event Handling                          |
        '          |                                                                     |
        '          +---------------------------------------------------------------------

        Private Sub ListBox_PreviewMouseLeftButtonDown(ByVal sender As Object, ByVal e As MouseButtonEventArgs)
            Dim pos = e.GetPosition(_listBox)
            Dim hitTestResult = VisualTreeHelper.HitTest(_listBox, pos)
            If hitTestResult Is Nothing Then
                Return
            End If
            Dim d = hitTestResult.VisualHit
            While d IsNot Nothing
                If TypeOf d Is ListBoxItem Then
                    e.Handled = True
                    Exit While
                End If
                d = VisualTreeHelper.GetParent(d)
            End While
        End Sub

        Private Sub ListBox_PreviewMouseMove(ByVal sender As Object, ByVal e As MouseEventArgs)
            If Mouse.Captured IsNot Nothing Then
                Return
            End If
            Dim pos = e.GetPosition(_listBox)
            Dim hitTestResult = VisualTreeHelper.HitTest(_listBox, pos)
            If hitTestResult Is Nothing Then
                Return
            End If
            Dim d = hitTestResult.VisualHit
            While d IsNot Nothing
                If TypeOf d Is ListBoxItem Then
                    Dim item = TryCast(d, ListBoxItem)
                    item.IsSelected = True
                    '                    _listBox.ScrollIntoView(item);
                    Exit While
                End If
                d = VisualTreeHelper.GetParent(d)
            End While
        End Sub

        Private Sub ListBox_MouseLeftButtonUp(ByVal sender As Object, ByVal e As MouseButtonEventArgs)
            Dim item As ListBoxItem = Nothing
            Dim d = TryCast(e.OriginalSource, DependencyObject)
            While d IsNot Nothing
                If TypeOf d Is ListBoxItem Then
                    item = TryCast(d, ListBoxItem)
                    Exit While
                End If
                d = VisualTreeHelper.GetParent(d)
            End While
            If item IsNot Nothing Then
                _popup.IsOpen = False
                UpdateText(TryCast(item.Content, String), True)
            End If
        End Sub

        '+---------------------------------------------------------------------+
        '          |                                                                     |
        '          |                 ResizeGrip Event Handling                           |
        '          |                                                                     |
        '          +---------------------------------------------------------------------

        Private Sub ResizeGrip_PreviewMouseLeftButtonDown(ByVal sender As Object, ByVal e As MouseButtonEventArgs)
            _downWidth = _chrome.ActualWidth + POPUP_SHADOW_DEPTH
            _downHeight = _chrome.ActualHeight + POPUP_SHADOW_DEPTH
            _downTop = _popup.VerticalOffset

            Dim p = e.GetPosition(_resizeGrip)
            p = _resizeGrip.PointToScreen(p)
            _ptDown = p

            _resizeGrip.CaptureMouse()
        End Sub

        Private Sub ResizeGrip_PreviewMouseMove(ByVal sender As Object, ByVal e As MouseEventArgs)
            If e.LeftButton <> MouseButtonState.Pressed Then
                Return
            End If
            Dim ptMove = e.GetPosition(_resizeGrip)
            ptMove = _resizeGrip.PointToScreen(ptMove)
            Dim dx = ptMove.X - _ptDown.X
            Dim dy = ptMove.Y - _ptDown.Y
            Dim newWidth = _downWidth + dx

            If newWidth <> _popup.Width AndAlso newWidth > 0 Then
                _popup.Width = newWidth
            End If
            If PopupOnTop Then
                Dim bottom = _downTop + _downHeight
                Dim newTop = _downTop + dy
                If newTop <> _popup.VerticalOffset AndAlso newTop < bottom - _popup.MinHeight Then
                    _popup.VerticalOffset = newTop
                    _popup.Height = bottom - newTop
                End If
            Else
                Dim newHeight = _downHeight + dy
                If newHeight <> _popup.Height AndAlso newHeight > 0 Then
                    _popup.Height = newHeight
                End If
            End If
        End Sub

        Private Sub ResizeGrip_PreviewMouseUp(ByVal sender As Object, ByVal e As MouseButtonEventArgs)
            _resizeGrip.ReleaseMouseCapture()
            If _popup.Width <> _downWidth OrElse _popup.Height <> _downHeight Then
                _manualResized = True
            End If
        End Sub

        '+---------------------------------------------------------------------+
        '          |                                                                     |
        '          |                    Window Event Handling                            |
        '          |                                                                     |
        '          +---------------------------------------------------------------------

        Private Sub OwnerWindow_Deactivated(ByVal sender As Object, ByVal e As EventArgs)
            If _popuponfocus Then
            Else
                _popup.IsOpen = False
            End If

        End Sub

        Private Sub OwnerWindow_PreviewMouseDown(ByVal sender As Object, ByVal e As MouseButtonEventArgs)
            If e.Source IsNot _textBox Then
                _popup.IsOpen = False
            End If
        End Sub

        Private Function HookHandler(ByVal hwnd As IntPtr, ByVal msg As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr, ByRef handled As Boolean) As IntPtr
            handled = False

            Select Case msg
                ' pass through
                Case WM_NCLBUTTONDOWN, WM_NCRBUTTONDOWN
                    _popup.IsOpen = False
                    Exit Select
            End Select
            Return IntPtr.Zero
        End Function

        '+---------------------------------------------------------------------+
        '          |                                                                     |
        '          |                    AcTb State And Behaviors                         |
        '          |                                                                     |
        '          +---------------------------------------------------------------------

        Private Function PopulatePopupList(ByVal items As IEnumerable(Of String))
            Dim text = _textBox.Text

            _listBox.ItemsSource = items
            If _listBox.Items.Count = 0 Then
                _popup.IsOpen = False
                Return Nothing
            End If
            Dim firstSuggestion = TryCast(_listBox.Items(0), String)
            If _listBox.Items.Count = 1 AndAlso text.Equals(firstSuggestion, StringComparison.OrdinalIgnoreCase) Then
                _popup.IsOpen = False
            Else
                _listBox.SelectedIndex = -1
                _textBeforeChangedByCode = text
                _scrollViewer.ScrollToHome()
                ShowPopup()

                '
                If AutoAppend AndAlso Not _supressAutoAppend AndAlso _textBox.SelectionLength = 0 AndAlso _textBox.SelectionStart = _textBox.Text.Length Then
                    _textChangedByCode = True
                    Try
                        Dim appendText As String
                        Dim appendProvider = TryCast(_dataProvider, IAutoAppendDataProvider)
                        If appendProvider IsNot Nothing Then
                            appendText = appendProvider.GetAppendText(text, firstSuggestion)
                        Else
                            appendText = firstSuggestion.Substring(_textBox.Text.Length)
                        End If
                        If Not String.IsNullOrEmpty(appendText) Then
                            _textBox.SelectedText = appendText
                        End If
                    Finally
                        _textChangedByCode = False
                    End Try
                End If
            End If
        End Function

        Private Property PopupOnTop() As Boolean
            Get
                Return _popupOnTop
            End Get
            Set(ByVal value As Boolean)
                If _popupOnTop = value Then
                    Return
                End If
                _popupOnTop = value
                If _popupOnTop Then
                    _resizeGrip.VerticalAlignment = VerticalAlignment.Top
                    _scrollBar.Margin = New Thickness(0, SystemParameters.HorizontalScrollBarHeight, 0, 0)
                    _resizeGrip.LayoutTransform = New ScaleTransform(1, -1)
                    _resizeGrip.Cursor = Cursors.SizeNESW
                Else
                    _resizeGrip.VerticalAlignment = VerticalAlignment.Bottom
                    _scrollBar.Margin = New Thickness(0, 0, 0, SystemParameters.HorizontalScrollBarHeight)
                    _resizeGrip.LayoutTransform = Transform.Identity
                    _resizeGrip.Cursor = Cursors.SizeNWSE
                End If
            End Set
        End Property

        Private Sub ShowPopup()
            If _textBox.IsFocused Then


                Dim popupOnTop__1 = False

                Dim p = New Point(0, _textBox.ActualHeight)
                p = _textBox.PointToScreen(p)
                Dim tbBottom = p.Y

                p = New Point(0, 0)
                p = _textBox.PointToScreen(p)
                Dim tbTop = p.Y

                _popup.HorizontalOffset = p.X
                Dim popupTop = tbBottom

                If Not _manualResized Then
                    _popup.Width = _textBox.ActualWidth + POPUP_SHADOW_DEPTH
                End If

                Dim popupHeight As Double
                If _manualResized Then
                    popupHeight = _popup.Height
                Else
                    Dim visibleCount = Math.Min(16, _listBox.Items.Count + 1)
                    popupHeight = visibleCount * _itemHeight + POPUP_SHADOW_DEPTH
                End If
                Dim screenHeight = SystemParameters.PrimaryScreenHeight
                If popupTop + popupHeight > screenHeight Then
                    If screenHeight - tbBottom > tbTop Then
                        popupHeight = SystemParameters.PrimaryScreenHeight - popupTop
                    Else
                        popupOnTop__1 = True
                        popupTop = tbTop - popupHeight + 4
                        If popupTop < 0 Then
                            popupTop = 0
                            popupHeight = tbTop + 4
                        End If
                    End If
                End If
                PopupOnTop = popupOnTop__1
                _popup.Height = popupHeight
                _popup.VerticalOffset = popupTop

                _popup.IsOpen = True
            End If
        End Sub

        Private Sub UpdateText(ByVal text As String, ByVal selectAll As Boolean)
            _textChangedByCode = True
            _textBox.Text = text
            If selectAll Then
                _textBox.SelectAll()
            Else
                _textBox.SelectionStart = text.Length
            End If
            _textChangedByCode = False
        End Sub
        Private Shared Function InlineAssignHelper(Of T)(ByRef target As T, ByVal value As T) As T
            target = value
            Return value
        End Function
    End Class
End Namespace
